Rem
mscchat server - a simple chat server, part of mscchat
(c) 2009 Michael Schaefer and Immo-Gert Birn

This program is free software: you can redistribute it And/Or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, Or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY Or FITNESS For A PARTICULAR PURPOSE.  See the
GNU General Public License For more details.

You should have received a copy of the GNU General Public License
along with this program.  If Not, see <http://www.gnu.org/licenses/>.
EndRem


SuperStrict 'Nie ohne SuperStrict!

Include "version.bmx"        ' SVN-Revision  
Include "chatconstanten.bmx" ' Constanten
Include "igblogging.bmx"     ' trace
Include "chathelper.bmx"     ' utility functions

Const VERSION:String = "MSC SUPER-CHAT-SERVER Version 1.2"

Const c_status_disconnected:Int = 0
Const c_status_connected:Int    = 1

Const passwdFileName:String = "mscpwd.msc" 

Global g_userlist:TList = CreateList()
Type t_user
  Field name:String
  Field pass:String
  Field rang:String
  Field expires:String
  Field status:Int = c_status_disconnected
EndType

Global trc:TTrace = New TTrace.Create(c_server_name, False, "s-", True)
trc.set_switch_file_name("servertrace.on.msc")

'*******************************************************************************
' Hauptprogramm
'*******************************************************************************

trc.trace( VERSION + " gestartet")
trc.trace( "SVN-Revision: " + svn_revision )
readUsers()
' Verbinden
Global hostObj:TGNetHost = IGBListen()
' remoteObj: empfangene Nachrichten
Global remoteObj:TGNetObject
' Liste der empfangenen Ojbekte
Global objList:TList = New TList 
' localObj: zum Senden von Nachrichten
Global localObj:TGNetObject = CreateGNetObject:TGNetObject(hostObj)
SetGNetString(localObj, c_slot_name, c_server_name)


Global text:String = ""
' Haupt-Programmschleife
While Not KeyHit(KEY_ESCAPE)

  Delay(50)
  IGBSync()

Wend

'*******************************************************************************
' Ende des Hauptprogramms
'*******************************************************************************
'------------------------------------------------------------------------------
' Funktionen
'------------------------------------------------------------------------------

Function readusers()

  Local text:String
  Local datei:TStream=OpenFile(passwdFileName)
  Local hasAdmin:Int = False
  If Not datei Then
    Print "Konnte Passwortdatei " + passwdFileName + " nicht finden! ENDE!"
    End
  EndIf
  While Not Eof (datei)
    If Not datei
      Exit
    Else
      If Not Eof (datei)
        text = ReadLine (datei)
      Else
        Exit
      EndIf
    EndIf
    text = text.Trim()
    If Not text Then Continue               ' Leerzeilen ignorieren
    If text.startswith("#") Then Continue   ' Kommentarzeilen ignorieren
    Local user:t_user = New t_user
    Local arr:String[] = text.split(":")
    Local l:Int = Len(arr)
    If l < 3 Then 
      Print "Ungueltige Zeile " + text
      Continue
    EndIf
    user.name = arr[0]
    user.pass = arr[1]
    user.rang = arr[2]
    If l = 4 Then user.expires = arr[3]
    If user.rang = c_rang_admin Then
      If hasAdmin = False Then 
        hasAdmin = True
      Else
        ' 2 Admins nicht erlaubt
        Print "Nur ein Admin moeglich! Enter-Taste zum Server-Beenden druecken!"
        Input
        End
      EndIf
    EndIf
    g_userlist.addlast(user)
    trc.trace( "Benutzer hinzugefuegt " + user.name + ", Rang " + user.rang)
  Wend
  CloseStream datei

EndFunction

'------------------------------------------------------------------------------

Function IGBListen:TGNetHost()
  Local isOk:Int
  Local hostObj:TGNetHost = CreateGNetHost()
  ' Server, lauscht (englisch: listen) und wartet auf Verbindung
  isOk = GNetListen(hostObj, getPort())
  If Not isOk Then
    RuntimeError "GNetListen ist schief gegangen!"
    CloseGNetHost(hostObj)
    End ' Programm beenden!
  EndIf
  trc.trace( "Listen ok auf Port: "+ getPort())
  Return hostObj
EndFunction

'------------------------------------------------------------------------------

Function getPort:Int()
  If AppArgs.length = 2 And AppArgs[1] = "-i" Then Return c_internetport
  Return c_port
EndFunction

'------------------------------------------------------------------------------

Function IGBsync()
  Local remote_name:String
  Local remote_txt:String
  Local remote_befehl:String
  Local remote_raum:String
  'trc.trace( "IGBSync")
  IGBGNetSync()
  objList = GNetObjects(hostObj, GNET_ALL)

  For remoteObj = EachIn objList
    If GNetObjectLocal(remoteObj) Then Continue
    If GNetObjectState(remoteObj) = GNET_SYNCED Then Continue
    trc.trace("IGBSync remoteObj gefunden (" + getStatusString(remoteObj) + ")")

    remote_name   = IGBGetAndTrcGnetstring( remoteObj, c_slot_name,   "IGBSync remote_name   = " )
    remote_befehl = IGBGetAndTrcGnetstring( remoteObj, c_slot_befehl, "IGBSync remote_befehl = " )
    remote_txt    = IGBGetAndTrcGnetstring( remoteObj, c_slot_text,   "IGBSync remote_txt    = " )
    remote_raum   = IGBGetAndTrcGnetstring( remoteObj, c_slot_raum,   "IGBSync remote_raum   = " )

    Local befehl:String = remote_befehl.Trim()
    If befehl.startsWith(c_befehl_anmelden) Then 
      anmelden(remote_name, remote_raum)
    Else
      Select remote_befehl
        Case C_BEFEHL_ABMELDEN
          abmelden(remote_name)
        Case C_BEFEHL_STOPSERVER
          ende(remote_name)
      EndSelect
    EndIf
  Next
        
EndFunction

'------------------------------------------------------------------------------

Function IGBGNetSync()
  Try
    GNetSync(hostObj)
    'trc.trace("sync")
  Catch ex:Object
    trc.trace( "sync exception" + ex.toString())
  EndTry
EndFunction


'------------------------------------------------------------------------------

Function IGBSetGNetString(slot:Int, wert:String)
  Try 
    SetGNetString(localObj, slot, wert)
    trc.trace( "IGBSetGNetString slot " + slot + ", Wert " + wert)
  Catch ex:Object
    trc.trace( "IGBSetGNetString slot " + slot + ": Exception " + ex.toString())
  EndTry
EndFunction


'------------------------------------------------------------------------------

Function anmelden:Int(name:String, raum:String)
  Local u_pw:String   = GetGNetString(remoteObj,c_slot_befehl).Replace(c_befehl_anmelden, "").Trim()
  Local user_found:Int = False
  Local is_ok:Int = False
  trc.trace("Anmelden: Anmeldeaufforderung von Benutzer " + name + ", Passwort " + u_pw )
  IGBSetGNetString(c_slot_name, c_server_name)
  IGBSetGNetString(c_slot_to_name, name)
  IGBSetGNetString(c_slot_raum, raum)
  For Local u:t_user = EachIn g_userlist
    If u.name.tolower() = name.tolower()
      If u.name <> name Then IGBSetGNetString(c_slot_to_name, u.name) ' Gross/Kleinschr. unters.
      trc.trace("Anmelden: Benutzer " + name + " gefunden")
      user_found = True
      If u.pass = u_pw Then 
        IGBSetGNetString(c_slot_rang, u.rang)
        u.status = c_status_connected
        is_ok = True
      EndIf
      Exit  'schleife verlassen
    EndIf
  Next
  If Not is_ok Then
    IGBSetGNetString(c_slot_befehl, c_error_anmeldung)
    IGBSetGNetString(c_slot_rang, "0")
  Else
    IGBSetGNetString(c_slot_befehl, c_ok_anmeldung)
  EndIf
  IGBGNetSync()
  Return is_ok
EndFunction

'------------------------------------------------------------------------------

Function abmelden(name:String)
  trc.trace("Abmelden: Abmeldeaufforderung von Benutzer " + name)
  For Local u:t_user = EachIn g_userlist
    If u.name = name
      'trc.trace("Abmelden: Benutzer " + name + " gefunden")
      u.status = C_STATUS_DISCONNECTED
      broadcast(name, c_status_abgemeldet)
    EndIf
  Next  
EndFunction

'------------------------------------------------------------------------------

' Nachricht an alle
Function broadcast(msg:String, befehl:String)
  IGBSetGNetString(c_slot_name, c_server_name)
  IGBSetGNetString(c_slot_to_name, c_an_alle)
  If msg Then IGBSetGNetString(c_slot_text, msg)
  If befehl Then IGBSetGNetString(c_slot_befehl, befehl)
  IGBGNetSync()
EndFunction

'------------------------------------------------------------------------------

Function ende(name:String)
  If name Then
    broadcast("Server wurde von " + name + " beendet. Bye!", c_befehl_stopserver)
  Else
    broadcast("Server wurde beendet. Bye!", c_befehl_stopserver)
  EndIf
  End
EndFunction

'------------------------------------------------------------------------------

Function IGBGetAndTrcGnetString:String(remoteObj:TGNetObject, slot:Int, txt:String)
  Try
    Local remote_str:String = GetGNetString(remoteObj,slot)
    trc.trace(txt + remote_str)
    Return remote_str
  Catch ex:Object
  EndTry
EndFunction